import { DocumentUtils, IData } from '@amcax/base';
import { CoreBasicCommands, ctx2, dataManager } from '@app-base/module';
import { nmAppErrorLog, nmAppLog, Renderer } from '@app-nextmesh/common/utils';
import { showLoading } from '@app-nextmesh/components';
import { documentV2 } from '@app-nextmesh/domain';
import { documentManager } from '@app-nextmesh/manager';
import {
  GspEventType,
  IGspEvent,
  ILoadParams,
  IPostprocessData,
  IShapeInfo,
  isIPostprocessData,
  ModelClippingChangeEvent,
  ModelEventType,
  VisualType,
} from '@app-nextmesh/types';
import {
  events,
  getCornerPoints,
  getMinMaxDistance,
  updateGeometriesPolygon,
  updateModelsColor,
} from '@app-nextmesh/utils';
import { createModelInfo } from '@app-nextmesh/utils/parseModel';
import { clearSelectedMeshes } from '@app-nextmesh/utils/picker';
import {
  clearField,
  clearModel,
  deleteModels,
  renderCurves,
  renderFields,
  renderMesh,
  renderModels,
} from '@app-nextmesh/utils/renderModel';
import _ from 'lodash';
import { Box3, Plane, Vector3 } from 'three-legacy';

class InitLoad {
  // 加载模型的参数
  private loadModelParams: ILoadParams = { type: null, data: null };
  // 加载模型场的参数
  private loadFieldParams: ILoadParams = { type: null, data: null };
  private colormap: string = null;
  private fileds: IData[] = [];
  // 更新模型是否加载完成
  private setModelLoaded: (loaded: boolean) => void;

  async initNextMesh(setModelLoaded: (loaded: boolean) => void) {
    this.setModelLoaded = setModelLoaded;
    await documentV2.load();
    let docInfo = await this.getDocInfo();
    if (docInfo.needUpdate) {
      await documentV2.sync();
      await CoreBasicCommands.clearTracks();
      docInfo = await this.getDocInfo();
    }

    // 保存几何的id
    const shapesDataMap = dataManager.shapesDataMap;
    docInfo.geometriesId = new Set(shapesDataMap.keys());
    // 保存几何的渲染数据
    docInfo.geometriesData = Array.from(shapesDataMap.values());
    documentManager.setDocumentInfo(docInfo);

    setModelLoaded(true);

    // 加载网格
    await this.loadMeshes();

    // 更新Component的颜色
    docInfo.geometries?.forEach(({ color, objects }) =>
      updateModelsColor(color, objects),
    );
    return docInfo;
  }

  /**
   * 获取当前所有模型的数据，shape的id及其顶点、边、面的数量
   * @returns
   */
  public getShapeInfo() {
    const shapesDataMap = dataManager.shapesDataMap;
    const shapeInfoMap: Map<string, IShapeInfo> = new Map();
    for (const [id, shapeData] of shapesDataMap.entries()) {
      if (shapeData == null) {
        continue;
      }
      const objectInfo = dataManager.getObjectInfoByDxid(id);
      if (objectInfo?.visibility === false) {
        continue;
      }

      const shapeInfo: IShapeInfo = {
        vertexCount: (shapeData as any).vertices?.topo?.length ?? 0,
        edgeCount: (shapeData as any).edges?.length ?? 0,
        faceCount: (shapeData as any).faces?.length ?? 0,
      };
      shapeInfoMap.set(id, shapeInfo);
    }
    return shapeInfoMap;
  }

  /**
   * 处理GSP事件，以后shape、mesh等的变化都可以在这里触发
   * @param event
   */
  public async handleGspEvent(event: IGspEvent) {
    try {
      switch (event.event) {
        case GspEventType.shapeChanged:
          {
            const renderData = await documentV2.getRenderData(event.data);
            await renderModels(renderData);
          }
          break;
        case GspEventType.meshChanged:
          await this.loadMeshes();
          break;
      }
    } catch (error) {
      nmAppErrorLog('handleGspEvent error', error);
    }
  }

  private async loadMeshes() {
    // 清除旧的网格
    deleteModels(documentManager.getMeshesId());
    documentManager.setMeshesId([]);

    updateGeometriesPolygon(true, 2, 2);
    const data = await documentV2.getVisualMesh2();
    const success = await renderModels(data, true, false);
    events.emit(ModelEventType.MeshesData, data);
    if (!success) {
      return;
    }
    documentManager.addMeshesId(data?.map((d) => d.id));
    documentManager.setMeshesData(data);
    nmAppLog('render mesh success');
  }

  public async doLoadModel(
    type: VisualType,
    uuid?: string,
    reload?: boolean,
    params?: any,
    clearAndShowLoading: boolean = true,
  ) {
    nmAppLog('InitLoad doLoadModel', {
      type,
      uuid,
      reload,
      params,
      clearAndShowLoading,
    });
    // 清除选中的模型的Map数据
    clearSelectedMeshes();
    this.loadFieldParams = {
      type: null,
      data: null,
    };
    const rm = this.loadModelParams;
    const newPrams: ILoadParams = {
      type: type,
      data: { uuid, params },
    };
    if (reload !== true) {
      // 不需要reload，且两次请求的参数一样，直接返回
      if (
        rm.data &&
        rm.data.uuid === uuid &&
        _.isEqual(rm.data.params, params)
      ) {
        return;
      }
    }

    this.loadModelParams = newPrams;
    this.setModelLoaded(false);

    this.fileds = [];

    const hide = clearAndShowLoading ? showLoading() : null;
    try {
      switch (type) {
        case VisualType.Geometry:
          await this.loadModel();
          nmAppLog('render model success');
          break;
        case VisualType.ResultMesh:
          {
            const data = await documentV2.resultRenderData(uuid);
            await renderModels(data, true, false);
            nmAppLog('render resultMesh success');
          }
          break;
        case VisualType.Visualization:
          {
            // 清除场景中的模型等，防止加载失败，显示错误的模型
            clearAndShowLoading && clearModel();
            const data = await documentV2.generatePostporeccess(uuid, params);
            const modelData = data.meshes;
            await renderModels(modelData, params?.showWireframe ?? true);
            nmAppLog('render postprocess success');
            if (data.extra) {
              events.emit(ModelEventType.SetExtra, data.extra);
            }
            if (data.fields) {
              this.fileds = data.fields;
              this.rerenderFields(data.fields, this.colormap);
              nmAppLog('render field success');
            }
            if (data.curves) {
              renderCurves(data.curves);
            }
          }
          break;
        case VisualType.MeshQuality:
          {
            const data = await documentV2.queryMeshQuality(params);
            if (data?.mesh) {
              renderMesh([data.mesh]);
            }
          }
          break;
        default:
          break;
      }
      this.setModelLoaded(true);
    } catch (error) {
      nmAppErrorLog('InitLoad doLoadModel', { error });
    } finally {
      hide?.();
    }
  }

  /**
   * 将缓存的渲染数据（如果有），保存到 Renderer 中
   */
  public saveShapeDatas() {
    const objects = documentManager.getGeometriesData();
    if (objects?.length > 0) {
      Renderer().saveShapeDatas(objects);
    }
  }

  private async loadModel() {
    this.saveShapeDatas();

    const objects = [];
    const components = documentManager.getComponents();
    if (components?.length > 0) {
      components.forEach((component) => {
        const objs = component.objects;
        if (objs?.length > 0) {
          objects.push(...objs);
        }
      });
    }
    const modelDxids: string[] = [];
    // 循环生成objectsInfo
    objects.forEach(({ id }) => {
      if (id != null) {
        dataManager.addObjectsInfo(createModelInfo(id, true));
        modelDxids.push(id);
      }
    });
    // 渲染数据已经在 Renderer 中了，直接 drawNewModels 即可
    await Renderer().drawNewModels(modelDxids);
  }

  public async doLoadField(
    type: VisualType,
    uuid: string,
    params: any,
    isload: boolean = true,
  ) {
    nmAppLog('InitLoad doLoadField1', { type, uuid, params });
    if (!uuid) {
      // 如果没有uuid，则将旧的模型移除
      this.fileds = [];
      clearField();
      this.loadFieldParams = {
        type: type,
        data: { uuid, params },
      };
      return;
    }
    const rm = this.loadFieldParams;
    const newPrams: ILoadParams = {
      type: type,
      data: { uuid, params },
    };
    // 两次请求的参数一样，直接返回
    if (
      rm.data &&
      rm.data.uuid === uuid &&
      _.isEqual(rm.data.params, params) &&
      isload
    ) {
      return;
    }
    nmAppLog('InitLoad doLoadField2', { type, uuid, params });
    this.loadFieldParams = newPrams;
    const hide = showLoading();
    try {
      const data: IData[] | IPostprocessData = await documentV2.resultData(
        uuid,
        params,
      );
      this.fileds = [];
      clearField();
      if (isIPostprocessData(data)) {
        events.emit(ModelEventType.SetExtra, data.extra);
        this.fileds = data.fields;
        renderFields(data.fields, this.colormap);
      }
    } catch (error) {
      nmAppErrorLog('InitLoad doLoadField', { error });
    } finally {
      hide();
    }
  }

  public rerenderFields(fs: IData[], cm: string) {
    if (fs.length > 0) {
      clearField();
      renderFields(fs, cm);
    }
  }

  private localPlane = new Plane(new Vector3(-1, 0, 0), 0.1);
  public clippingRender(event: ModelClippingChangeEvent) {
    const renderer = ctx2.viewer.sceneSetup.renderer;
    if (!event) {
      renderer.clippingPlanes = [];
      renderer.localClippingEnabled = false;
      ctx2.viewer.requestRender();
      return;
    }
    const { x, y, z, c } = event;
    renderer.localClippingEnabled = true;
    let newC = c;
    // 最小最大时，多一点偏移，避免穿透
    if (c == 0) {
      newC = -0.01;
    } else if (c == 1) {
      newC = 1.01;
    }
    const newV = new Vector3(x, y, z).normalize();
    const boundingBox = new Box3();
    boundingBox.setFromObject(ctx2.cadScene.workGroup);
    const center = boundingBox.getCenter(new Vector3());
    const boxCornerPoints = getCornerPoints(boundingBox);
    const minMax = getMinMaxDistance(newV, center, boxCornerPoints);
    const l = minMax.min + newC * (minMax.max - minMax.min);

    this.localPlane.constant = -l - newV.dot(center);
    this.localPlane.normal.copy(newV);

    ctx2.viewer.sceneSetup.renderer.clippingPlanes = [this.localPlane];

    ctx2.viewer.requestRender();
  }

  public async getDocInfo() {
    const docInfo = await documentV2.get();
    docInfo.collectionUuid = localStorage.getItem('COLLECTION_ID');
    docInfo.uuid = docInfo.documentId;
    docInfo.readonly = DocumentUtils.isReadOnly();
    const plugins = await documentV2.listSimulationPlugins();
    docInfo.simulationPlugins = plugins;
    return docInfo;
  }

  public handleSetColormap(cm: string) {
    if (!cm || this.colormap === cm) {
      return;
    }
    this.colormap = cm;
    this.rerenderFields(this.fileds, cm);
  }

  private static instance: InitLoad;
  public static getInstance() {
    if (!InitLoad.instance) {
      InitLoad.instance = new InitLoad();
    }
    return InitLoad.instance;
  }
}

export const initLoad = InitLoad.getInstance();
